﻿#region Copyright & License

//
// Author: Ian Davis <ian.f.davis@gmail.com> Copyright (c) 2007-2008, Ian Davs
//
// Portions of this software were developed for NUnit. See NOTICE.txt for more
// information. 
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//

#endregion

#region Using Directives

using System;
using System.Collections.Generic;
using Ensurance.Constraints.Binary;
using Ensurance.Constraints.Prefix;

#endregion

namespace Ensurance.Constraints
{
    /// <summary>
    /// The Constraint class is the base of all built-in or user-defined
    /// constraints in NUnit. It provides the operator overloads used to combine
    /// constraints.
    /// </summary>
    public abstract class Constraint<T> : IConstraint<T>
    {
        /// <summary>
        /// Flag the constraint to ignore case and return self.
        /// </summary>
        public virtual Constraint<T> IgnoreCase
        {
            get
            {
                CaseInsensitive = true;
                return this;
            }
        }

        /// <summary>
        /// Flag the constraint to compare arrays as collections and return
        /// self.
        /// </summary>
        public Constraint<T> AsCollection
        {
            get
            {
                CompareAsCollection = true;
                return this;
            }
        }

        /// <summary>
        /// The actual value being tested against a constraint
        /// </summary>
        protected internal object Actual { get; set; }

        /// <summary>
        /// If true, all string comparisons will ignore case
        /// </summary>
        protected internal bool CaseInsensitive { get; set; }

        /// <summary>
        /// If true, arrays will be treated as collections, allowing those of
        /// different dimensions to be compared
        /// </summary>
        protected internal bool CompareAsCollection { get; set; }

        /// <summary>
        /// IComparer object used in comparisons for some constraints.
        /// </summary>
        protected internal IComparer<T> CompareWith { get; set; }

        /// <summary>
        /// If non-zero, equality comparisons within the specified tolerance
        /// will succeed.
        /// </summary>
        protected internal object Tolerance { get; set; }

        #region IConstraint<T> Members

        /// <summary>
        /// Write the failure message to the MessageWriter provided as an
        /// argument. The default implementation simply passes the constraint
        /// and the actual value to the writer, which then displays the
        /// constraint description and the value.
        /// 
        /// Constraints that need to provide additional details, such as where
        /// the error occured can override this.
        /// </summary>
        /// <exception cref="ArgumentNullException">if the message writer is null.</exception>
        /// <param name="writer">The MessageWriter on which to display the message</param>
        public virtual void WriteMessageTo( IMessageWriter writer )
        {
            if ( writer == null )
            {
                throw new ArgumentNullException( "writer" );
            }
            writer.DisplayDifferences( this );
        }

        /// <summary>
        /// Determines whether [is satisfied by] [the specified value].
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        /// 	<c>true</c> if [is satisfied by] [the specified value]; otherwise, <c>false</c>.
        /// </returns>
        public abstract bool IsSatisfiedBy( T value );

        /// <summary>
        /// Write the constraint description to a MessageWriter
        /// </summary>
        /// <param name="writer">The writer on which the description is displayed</param>
        public abstract void WriteDescriptionTo( IMessageWriter writer );

        /// <summary>
        /// Write the actual value for a failing constraint test to a
        /// MessageWriter. The default implementation simply writes the raw
        /// value of actual, leaving it to the writer to perform any formatting.
        /// </summary>
        /// <exception cref="ArgumentNullException">if the message writer is null.</exception>
        /// <param name="writer">The writer on which the actual value is displayed</param>
        public virtual void WriteActualValueTo( IMessageWriter writer )
        {
            if ( writer == null )
            {
                throw new ArgumentNullException( "writer" );
            }
            writer.WriteActualValue( Actual );
        }

        #endregion

        /// <summary>
        /// Flag the constraint to use a tolerance when determining equality.
        /// Currently only used for doubles and floats.
        /// </summary>
        /// <param name="tolerance">Tolerance to be used</param>
        /// <returns>Self.</returns>
        public Constraint<T> Within( object tolerance )
        {
            Tolerance = tolerance;
            return this;
        }

        /// <summary>
        /// Flag the constraint to use the supplied IComparer object.
        /// </summary>
        /// <param name="comparer">The IComparer object to use.</param>
        /// <returns>Self.</returns>
        public Constraint<T> Comparer( IComparer<T> comparer )
        {
            CompareWith = comparer;
            return this;
        }

        #region Operator Overloads

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied only if both 
        /// argument constraints are satisfied.
        /// </summary>
        public static Constraint<T> operator &( Constraint<T> left, Constraint<T> right )
        {
            return new AndConstraint<T>( left, right );
        }

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied only if both 
        /// argument constraints are satisfied.
        /// </summary>
        public static Constraint<T> BitwiseAnd( Constraint<T> left, Constraint<T> right )
        {
            return new AndConstraint<T>( left, right );
        }

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied if either of
        /// the argument constraints is satisfied.
        /// </summary>
        public static Constraint<T> operator |( Constraint<T> left, Constraint<T> right )
        {
            return new OrConstraint<T>( left, right );
        }

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied if either of
        /// the argument constraints is satisfied.
        /// </summary>
        public static Constraint<T> BitwiseOr( Constraint<T> left, Constraint<T> right )
        {
            return new OrConstraint<T>( left, right );
        }

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied if the argument
        /// Constraint<T> is not satisfied.
        /// </summary>
        public static Constraint<T> operator !( Constraint<T> m )
        {
            return new NotConstraint<T>( m ?? new EqualConstraint<T>( default( T ) ) );
        }

        /// <summary>
        /// This operator creates a Constraint<T> that is satisfied if the argument
        /// Constraint<T> is not satisfied.
        /// </summary>
        public static Constraint<T> LogicalNot( Constraint<T> m )
        {
            return new NotConstraint<T>( m ?? new EqualConstraint<T>( default( T ) ) );
        }

        #endregion
    }
}